import { EditorBarrier } from './EditorBarrier';
import { EditorArrow } from './EditorArrow';
import { EditorGlassInspector } from './EditorGlassInspector';
import { EditorBlock } from './EditorBlock';
import { _decorator, Node, color, KeyCode, v3 } from 'cc';
import { BaseView } from '../../../../scripts/libs/rxjs/cc3/BaseView';
import { AxisLockType, findShapeId, shapeInfoMap, StageInfo } from '../../game/basic';
import { Color } from 'cc';
import { find } from 'cc';
import { v2 } from 'cc';
import { Vec3 } from 'cc';
import { Prefab } from 'cc';
import { instantiate } from 'cc';
import { BindNodeTouchWithClick, touchLocationToWorldPosition } from '../../../../scripts/libs/rxjs/cc3/BindNode';
import { lm } from '../../LobbyManager';
import { EventTouch } from 'cc';
import { gm } from '../../../../scripts/managers/GameManager';
import { UITransform } from 'cc';
import { isValid } from 'cc';
import { KeyDown } from '../../../../scripts/libs/rxjs/cc3/KeyEvent';
import { EditorBlockInspector } from './EditorBlockInspector';
import { ReactiveProperty } from '../../../../scripts/libs/rxjs/cc3/ReactiveProperty';
import { Observable, Subscription, first, subscribeOn } from 'rxjs';
import { TweenToStart } from '../../../../scripts/libs/rxjs/cc3/TweenStart';
import { nc } from '../../../../scripts/libs/rxjs/NotificationCenter';
import { EditorGlass } from './EditorGlass';
import { EditorGlassExplorer } from './EditorGlassExplorer';
import { Sprite } from 'cc';
import { EditorBlockExplorer } from './EditorBlockExplorer';
import { EditorArrowInspector } from './EditorArrowInspector';
import { EditorGround } from './EditorGround';
import { EditorArrowExplorer } from './EditorArrowExplorer';
import { deepclone } from '../../../../scripts/libs/utils/deepclone/deepclone';
import { EditorStageInspector } from './EditorStageInspector';
import { size } from 'cc';
import { StringTools } from '../../../../scripts/libs/utils/StringTools';
const { ccclass, property } = _decorator;

const _v20 = v2();
const _v30 = v3();

@ccclass('EditorStageView')
export class EditorStageView extends BaseView {

    _color = color().fromHEX('#c0491f');
    get logColorOverride(): Color {
        return this._color;
    }

    @property(Prefab)
    editorGroundPrefab: Prefab = null;

    @property(Prefab)
    editorIndicatorPrefab: Prefab = null;

    @property(Prefab)
    editorBlockPrefab: Prefab = null;

    @property(Prefab)
    editorArrowPrefab: Prefab = null;

    @property(Prefab)
    editorBarrierPrefab: Prefab = null;

    @property(Prefab)
    editorBlockInspectorPrefab: Prefab = null;

    @property(Prefab)
    editorArrowInspectorPrefab: Prefab = null;

    @property(Prefab)
    editorGlassPrefab: Prefab = null;

    @property(Prefab)
    editorGlassInspectorPrefab: Prefab = null;

    @property(Prefab)
    editorGlassExplorerPrefab: Prefab = null;

    @property(Prefab)
    editorBlockExplorerPrefab: Prefab = null;

    @property(Prefab)
    editorArrowExplorerPrefab: Prefab = null;

    @property(Prefab)
    editorStageInspectorPrefab: Prefab = null;

    @property(Prefab)
    editorObstaclePrefab: Prefab = null;

    editorIndicator: Node = null;
    indicatorContainer: Node = null;

    stageInfo: StageInfo = null;

    stageNum = ReactiveProperty.Create<number>(0);
    time = ReactiveProperty.Create<number>(180);
    hard = ReactiveProperty.Create<number>(0);
    tip = ReactiveProperty.Create<string>('');

    groundContainer: Node = null;
    glassContainer: Node = null;
    blockContainer: Node = null;
    arrowContainer: Node = null;

    inspectorContainer: Node = null;

    blockInspectorMap = new Map<number, EditorBlockInspector>();
    arrowInspectorMap = new Map<number, EditorArrowInspector>();
    glassInspectorMap = new Map<number, EditorGlassInspector>();



    groupIdSelectIndicator = ReactiveProperty.Create<Node>(null); // 指定groupdId 的指示器，也表示当前进入指定指示器的模式

    get TotalCellCount() {
        if (this.stageInfo != null) {
            return this.stageInfo.width * this.stageInfo.height;
        }
        return 0;
    }

    willBind(): void {
        globalThis.editor2d = this;
        this.groundContainer = find('groundContainer', this.node);
        this.glassContainer = find('glassContainer', this.node);
        this.blockContainer = find('blockContainer', this.node);
        this.arrowContainer = find('arrowContainer', this.node);
        this.indicatorContainer = find('indicatorContainer', this.node);
    }

    colorSelectedStr: string = null;

    useObserves() {
        return [
            BindNodeTouchWithClick(this.node, lm.uiRoot.camera).subscribe(this.handleTouch.bind(this)),
            lm.touchGame.subscribe(eventTouch => {
                eventTouch.getLocation(_v20);
                touchLocationToWorldPosition(lm.uiRoot.camera, _v20, _v30);
                if (eventTouch.type == 'touch-start') {
                } else {
                    this.handleTouch({ type: eventTouch.type, eventTouch, worldPosition: _v30 })
                }
            }),
            gm.editPosSelected.subscribe(_ => {
                if (_ != null) {
                    if (!isValid(this.editorIndicator)) {
                        this.editorIndicator = instantiate(this.editorIndicatorPrefab);
                        this.editorIndicator.setParent(this.indicatorContainer);
                    }
                    this.editorIndicator.setPosition(this.getPositionByIdx(_));
                } else if (_ == null && isValid(this.editorIndicator)) {
                    this.editorIndicator.destroy();
                    this.editorIndicator = null;
                }

                // this.tryOpenBlockInspector();
            }),
            KeyDown().subscribe((_: KeyCode) => {
                if (_ == KeyCode.KEY_Z && gm.editCtrlPressed.value) {
                    this.undo();
                } else if (_ == KeyCode.ESCAPE) {
                    gm.editPosSelected.value = null;
                    this.closeAllInspectors();
                    gm.unfocusAllCommand.execute();
                } else if (_ == KeyCode.BACKSPACE || _ == KeyCode.DELETE) {
                    if (gm.editPosSelected.value != null) {
                        this.removeBlock(gm.editPosSelected.value);
                        this.remakeStageInfo();
                    }
                } else if (_ == KeyCode.KEY_R) {
                    if (this.draggedEditorArrow.value != null) {
                        let direction = (this.draggedEditorArrow.value.direction + 1) % 4;
                        this.draggedEditorArrow.value.direction = direction;
                    }
                } else if (_ == KeyCode.NUM_0 || _ == KeyCode.DIGIT_0) {
                    // gm.editColorSelected.value = 0;
                    if (this.colorSelectedStr != null) this.colorSelectedStr += "0";
                    else this.colorSelectedStr = "0";
                } else if (_ == KeyCode.NUM_1 || _ == KeyCode.DIGIT_1) {
                    if (this.colorSelectedStr != null) this.colorSelectedStr += "1";
                    else gm.editColorSelected.value = 1;
                } else if (_ == KeyCode.NUM_2 || _ == KeyCode.DIGIT_2) {
                    if (this.colorSelectedStr != null) this.colorSelectedStr += "2"
                    else gm.editColorSelected.value = 2;
                } else if (_ == KeyCode.NUM_3 || _ == KeyCode.DIGIT_3) {
                    if (this.colorSelectedStr != null) this.colorSelectedStr += "3"
                    else gm.editColorSelected.value = 3;
                } else if (_ == KeyCode.NUM_4 || _ == KeyCode.DIGIT_4) {
                    if (this.colorSelectedStr != null) this.colorSelectedStr += "4"
                    else gm.editColorSelected.value = 4;
                } else if (_ == KeyCode.NUM_5 || _ == KeyCode.DIGIT_5) {
                    if (this.colorSelectedStr != null) this.colorSelectedStr += "5"
                    else gm.editColorSelected.value = 5;
                } else if (_ == KeyCode.NUM_6 || _ == KeyCode.DIGIT_6) {
                    if (this.colorSelectedStr != null) this.colorSelectedStr += "6"
                    else gm.editColorSelected.value = 6;
                } else if (_ == KeyCode.NUM_7 || _ == KeyCode.DIGIT_7) {
                    if (this.colorSelectedStr != null) this.colorSelectedStr += "7"
                    else gm.editColorSelected.value = 7;
                } else if (_ == KeyCode.NUM_8 || _ == KeyCode.DIGIT_8) {
                    if (this.colorSelectedStr != null) this.colorSelectedStr += "8"
                    else gm.editColorSelected.value = 8;
                } else if (_ == KeyCode.NUM_9 || _ == KeyCode.DIGIT_9) {
                    if (this.colorSelectedStr != null) this.colorSelectedStr += "9"
                    else gm.editColorSelected.value = 9;
                }
                if (this.colorSelectedStr?.length >= 3) {
                    gm.editColorSelected.value = Number(this.colorSelectedStr);
                    this.colorSelectedStr = null;
                }
            }),
            nc.subscribe<{ shapeId: number }>('create_shape', _ => {
                let { shapeId } = _;
                this.log('@create_shape', gm.editToolsSelected.value, shapeId, gm.editColorSelected.value);

                if (gm.editToolsSelected.value == 'block') {
                    let editorBlock = this.addBlock(0, shapeId);
                    this.draggedEditorBlock.value = editorBlock;
                } else if (gm.editToolsSelected.value == 'ice_block') {
                    let editorBlock = this.addBlock(0, shapeId);
                    editorBlock.ice.value = 1;
                    this.draggedEditorBlock.value = editorBlock;
                } else if (gm.editToolsSelected.value == 'locker_block') {
                    let editorBlock = this.addBlock(0, shapeId);
                    editorBlock.lockerColor.value = 1;
                    this.draggedEditorBlock.value = editorBlock;
                } else if (gm.editToolsSelected.value == 'glass') {
                    let editorGlass = this.addGlass(0, shapeId);
                    this.draggedEditorGlass.value = editorGlass;
                } else if (gm.editToolsSelected.value == 'wood_block') {
                    gm.editColorSelected.value = 0;
                    let editorBlock = this.addBlock(0, shapeId);
                    editorBlock.colorId = 0;
                    this.draggedEditorBlock.value = editorBlock;
                }

            }),

            nc.subscribe<{ shapeId: number }>('create_arrow', _ => {
                let { shapeId } = _;
                this.log('@create_arrow', gm.editToolsSelected.value, shapeId, gm.editColorSelected.value);

                this.closeAllArrowInspectors(false);
                // if (gm.editToolsSelected.value == 'arrow') {
                let editorArrow = this.addArrow(0, shapeId);
                this.draggedEditorArrow.value = editorArrow;
                // }

            }),

            gm.editCtrlPressed.subscribe(_ => {
                for (let [glassId, editorGlass] of this.glassMap) {
                    if (this.glassInspectorMap.has(glassId)) {
                        editorGlass.node.active = true;
                    } else {
                        editorGlass.node.active = !_;
                    }
                }

                for (let [blockId, editorBlock] of this.blockMap) {
                    if (this.blockInspectorMap.has(blockId)) {
                        editorBlock.node.active = true;
                    } else {
                        editorBlock.node.active = !_;
                    }
                }
            })
        ];
    }

    onDestroyView(): void {
        if (this.groupIdSelectIndicator.value != null) {
            this.groupIdSelectIndicator.value.destroy();
            this.groupIdSelectIndicator.value = null;
        }
    }

    static Space = v2(100, 100);

    static getPosByOffsetRowCol(row: number, col: number) {
        return v3(col * this.Space.x, -row * this.Space.y, 0);
    }

    get Width(): number {
        return this.stageInfo?.width ?? 0;
    }

    get Height(): number {
        return this.stageInfo?.height ?? 0;
    }

    isEdgeWall(idx: number) { // 这个格子是墙？最边缘
        let [r, c] = this.getRowColPos(idx);
        return r == 0 || r == this.Height - 1 || c == 0 || c == this.Width - 1;
    }

    isInRange(row: number, col: number) {
        return row >= 0 && row < this.Height && col >= 0 && col < this.Width;
    }

    getIdxByRowCol(rc: number[]) {
        return rc[0] * this.Width + rc[1];
    }
    getRowColPos(idx: number): number[] {
        return [Math.floor(idx / this.Width), idx % this.Width]; // 得到行列坐标，上到下，左到右
    }
    getPositionByIdx(idx: number) {
        let [row, col] = this.getRowColPos(idx);
        return this.getPositionByRowCol(row, col);
    }
    getPositionByRowCol(row: number, col: number) {
        let x = col * EditorStageView.Space.x - EditorStageView.Space.x * this.Width / 2 + EditorStageView.Space.x / 2;
        let y = row * -EditorStageView.Space.y + EditorStageView.Space.y * this.Height / 2 - EditorStageView.Space.y / 2;
        return v3(x, y, 0);
    }
    getRowColByPos(position: Vec3) {
        let x = Math.floor((position.x + EditorStageView.Space.x * this.Width / 2) / EditorStageView.Space.x);
        let y = this.Height - Math.floor((position.y + EditorStageView.Space.y * this.Height / 2) / EditorStageView.Space.y) - 1;
        return [y, x];
    }
    getIdxByPos(position: Vec3) {
        let [row, col] = this.getRowColByPos(position);
        if (row < 0 || row >= this.Height) return null;
        if (col < 0 || col >= this.Width) return null;
        return this.getIdxByRowCol([row, col]);
    }
    getAdjoin1(posId) {
        let [row, col] = this.getRowColPos(posId);
        let list = [
            { data: [row, col + 1], direction: 0 },
            { data: [row - 1, col], direction: 1 },
            { data: [row, col - 1], direction: 2 },
            { data: [row + 1, col], direction: 3 }
        ];
        return list.filter(_ => {
            let [row, col] = _.data;
            return row >= 0 && row < this.Height && col >= 0 && col < this.Width;
        }).map(_ => {
            let [row, col] = _.data;
            return { idx: this.getIdxByRowCol([row, col]), direction: _.direction };
        });
    }
    genShapePos(posId: number, shapeId: number) { // 根据blockId计算block的中心位置
        let shapeInfo = shapeInfoMap.get(shapeId);
        let rcPos = this.getRowColPos(posId);
        rcPos[0] += shapeInfo.center[0];
        rcPos[1] += shapeInfo.center[1];
        return this.getPositionByRowCol(rcPos[0], rcPos[1]);
    }

    loadStage(stageInfo: StageInfo) {
        this.log('loadStage', stageInfo);
        this.stageInfo = stageInfo;
        this.closeAllInspectors(true);
        this.groundMap.clear();
        this.barrierMap.clear();
        this.groundContainer.destroyAllChildren();
        this.blockMap.clear();
        this.blockContainer.destroyAllChildren();
        this.glassMap.clear();
        this.glassContainer.destroyAllChildren();
        this.arrowMap.clear();
        this.arrowContainer.destroyAllChildren();

        this.stageNum.value = this.stageInfo.stageId ?? 0;
        this.time.value = this.stageInfo.time ?? 0;
        this.hard.value = this.stageInfo.hard ?? 0;
        this.tip.value = this.stageInfo.tip ?? '';

        this.genGrids();
        // this.closeGlassExplorer();
        // this.tryOpenGlassExplorer();
        this.closeBlockExplorer();
        this.tryOpenBlockExplorer();

        this.closeArrowExplorer();
        this.tryOpenArrowExplorer();

        this.closeStageInspector();
        this.tryOpenStageInspector();
    }

    groundMap = new Map<number, EditorGround>();
    barrierMap = new Map<number, EditorBarrier>();
    genGrids() {
        if (this.stageInfo.walls == null) this.stageInfo.walls = [];
        let wallSet = new Set(this.stageInfo.walls);
        for (let i = 0; i < this.TotalCellCount; i++) {

            if (this.isEdgeWall(i)) wallSet.add(i);

            let groundNode = instantiate(this.editorGroundPrefab);
            groundNode.setParent(this.groundContainer);
            groundNode.setPosition(this.getPositionByIdx(i));
            let editorGround = groundNode.getComponent(EditorGround);
            editorGround.posId.value = i;
            editorGround.isWall.value = wallSet.has(i);
            this.groundMap.set(i, editorGround);

        }

        if (this.stageInfo.barriers == null) this.stageInfo.barriers = [];
        for (let i = 0; i < this.stageInfo.barriers.length; i++) {
            let [posId, colorId] = this.stageInfo.barriers[i];
            this.addBarrier(posId, colorId);
        }

        if (this.stageInfo.blocks == null) this.stageInfo.blocks = [];
        for (let i = 0; i < this.stageInfo.blocks.length; i++) {
            let blockInfo = this.stageInfo.blocks[i];
            let editorBlock = this.addBlock(blockInfo.pos, blockInfo.shape, blockInfo.id);
            editorBlock.group.value = blockInfo.group ?? this.genBlockGroupId();
            editorBlock.layer.value = blockInfo.layer ?? 0;
            editorBlock.colorId = blockInfo.color;
            editorBlock.axisLock = blockInfo.axisLock ?? AxisLockType.None;
            editorBlock.axisLockParam.value = blockInfo.axisLockParam ?? '';
            editorBlock.keys.value = blockInfo.keys;
            editorBlock.lockerColor.value = blockInfo.locker;
            editorBlock.ice.value = blockInfo.ice ?? 0;
            editorBlock.blind.value = blockInfo.blind ?? 0;
            editorBlock.noBlindTxt.value = blockInfo.noBlindTxt ?? 0;
        }

        if (this.stageInfo.arrows == null) this.stageInfo.arrows = [];
        for (let i = 0; i < this.stageInfo.arrows.length; i++) {
            let arrowInfo = this.stageInfo.arrows[i];
            let editorArrow = this.addArrow(arrowInfo.pos, arrowInfo.direction, arrowInfo.id);
            editorArrow.layer.value = arrowInfo.layer ?? 0;
            editorArrow.direction = arrowInfo.direction ?? 0;
            editorArrow.colorId = arrowInfo.color;
            editorArrow.prev.value = arrowInfo.prev;
            editorArrow.ice.value = arrowInfo.ice ?? 0;
            this.makeMirrorArrow(editorArrow);
        }

        if (this.stageInfo.glasses == null) this.stageInfo.glasses = [];
        for (let i = 0; i < this.stageInfo.glasses.length; i++) {
            let glassInfo = this.stageInfo.glasses[i];
            let editorGlass = this.addGlass(glassInfo.pos, glassInfo.shape, glassInfo.id);
            editorGlass.nails.value = glassInfo.nails ?? [];
            editorGlass.layer.value = glassInfo.layer ?? 0;
            editorGlass.lock.value = glassInfo.lock ?? 0;
        }
    }

    _v30 = v3();

    draggedEditorBlock = ReactiveProperty.Create<EditorBlock>(null);
    draggedEditorArrow = ReactiveProperty.Create<EditorArrow>(null);
    draggedEditorGlass = ReactiveProperty.Create<EditorGlass>(null);

    draggedOffset = v3(0, 0, 0);

    _findGlassOffset = 0; // 可以选择不同层级的玻璃
    _findArrowOffset = 0; // 可以选择不同层级的玻璃
    _findBlockOffset = 0; // 可以选择不同层级的玻璃

    dragGroupedList: { editorBlock: EditorBlock, offset: Vec3 }[] = []; // 拖动block 时，同组的其它 editorBlock 记录在这里

    handleTouch(param: { type: string, eventTouch: EventTouch, worldPosition: Vec3 }) {
        let { type, worldPosition } = param;
        if (gm.editAltPressed.value) {
            this.handleTouchInEditModePanStage(param);
            return;
        }

        switch (type) {
            case 'touch-start': {
                let editorBlocks = this.findBlockByWorldPos(worldPosition);
                let editorBlock = null;
                if (gm.editShiftPressed.value) {
                    this._findBlockOffset++;
                }
                if (!gm.editShiftPressed.value) { // 优先选择带有 inspector的
                    editorBlock = editorBlocks.filter(_ => this.blockInspectorMap.has(_.blockId.value))?.[this._findBlockOffset % editorBlocks.length];
                }
                if (editorBlock == null) {
                    editorBlock = editorBlocks[this._findBlockOffset % editorBlocks.length];
                }
                if (this.draggedEditorBlock.value == null && editorBlock != null) {
                    this.draggedEditorBlock.value = editorBlock;
                    let position = this.getComponent(UITransform).convertToNodeSpaceAR(worldPosition);
                    this.draggedOffset.set(position).subtract(editorBlock.node.position);
                    this.tryOpenBlockInspector(editorBlock.blockId.value);
                    this.fillDragGroupList(editorBlock);
                }

                let editorArrows = this.findArrowByWorldPos(worldPosition);
                let editorArrow = null;
                if (gm.editShiftPressed.value) {
                    this._findArrowOffset++;
                }
                // if (!gm.editShiftPressed.value) { // 优先选择带有 inspector的
                //     editorBlock = editorBlocks.filter(_ => this.glassInspectorMap.has(_.blockId.value))?.[this._findArrowOffset % editorBlocks.length];
                // }
                if (editorArrow == null) {
                    editorArrow = editorArrows[this._findArrowOffset % editorArrows.length];
                }
                if (this.draggedEditorArrow.value == null && editorArrow != null) {
                    this.draggedEditorArrow.value = editorArrow;
                    this.destroyMirrorArrow(editorArrow);
                    let position = this.getComponent(UITransform).convertToNodeSpaceAR(worldPosition);
                    this.draggedOffset.set(position).subtract(editorArrow.node.position);
                    this.tryOpenArrowInspector(editorArrow.arrowId.value);
                }

                break;
            }
            case 'touch-move': {
                if (this.groupIdSelectIndicator.value != null) {
                    this.groupIdSelectIndicator.value.setWorldPosition(worldPosition);
                } else if (this.draggedEditorBlock.value != null) {
                    let position = this.getComponent(UITransform).convertToNodeSpaceAR(worldPosition);
                    let targetPosition = v3(position).subtract(this.draggedOffset);
                    this._v30.set(targetPosition);
                    this.draggedEditorBlock.value.node.setPosition(this._v30);
                    this.blockInspectorMap.get(this.draggedEditorBlock.value.blockId.value)?.refreshGraphics();
                    this.moveDragGroupList(this._v30);
                } else if (this.draggedEditorArrow.value != null) {
                    let position = this.getComponent(UITransform).convertToNodeSpaceAR(worldPosition);
                    let targetPosition = v3(position).subtract(this.draggedOffset);
                    this._v30.set(targetPosition);
                    this.draggedEditorArrow.value.node.setPosition(this._v30);
                    this.arrowInspectorMap.get(this.draggedEditorArrow.value.arrowId.value)?.refreshGraphics();
                }
                break;
            }
            case 'touch-cancel':
            case 'touch-end':
            case 'touch-end-click': {
                if (this.groupIdSelectIndicator.value != null) {
                    let editorBlocks = this.findBlockByWorldPos(worldPosition);
                    let blockInspector: EditorBlockInspector = this.blockInspectorMap.values().next().value;
                    // let layerId: number = blockInspector.editorBlock.value.layer.value ?? 0;
                    let _targetEditorBlock = editorBlocks?.[0];
                    if (_targetEditorBlock != null) {
                        blockInspector.editorBlock.value.group.value = _targetEditorBlock.group.value ?? 0;
                        blockInspector.editorBlock.again();
                        this.remakeStageInfo();
                    }
                    this.groupIdSelectIndicator.value.destroy();
                    this.groupIdSelectIndicator.value = null;
                }
                let editorBlock = this.draggedEditorBlock.value;
                if (editorBlock != null) {
                    this.endDragGroupList(this.draggedEditorBlock.value.node.position);
                    let zeroPosition = this.getZeroPosition(this.draggedEditorBlock.value.shapeId, this.draggedEditorBlock.value.node.position);
                    let targetIdx = this.getIdxByPos(zeroPosition);
                    if (targetIdx == null) { // 错误位置返回
                        this.setBlockToPositonId(editorBlock, editorBlock.posId.value);
                    } else {
                        this.setBlockToPositonId(editorBlock, targetIdx);
                        editorBlock.posId.value = targetIdx;
                    }
                    this.draggedEditorBlock.value = null;
                    this.remakeStageInfo();
                }
                let editorArrow = this.draggedEditorArrow.value;
                if (editorArrow != null) {
                    let targetIdx = this.getIdxByPos(this.draggedEditorArrow.value.node.position);
                    if (targetIdx == null) { // 错误位置返回
                        this.setArrowToPositonId(editorArrow, editorArrow.posId.value);
                    } else {
                        this.setArrowToPositonId(editorArrow, targetIdx);
                        editorArrow.posId.value = targetIdx;
                    }
                    this.makeMirrorArrow(editorArrow);
                    this.draggedEditorArrow.value = null;
                    this.remakeStageInfo();
                }


                // let editorGlass = this.draggedEditorGlass.value;
                // if (editorGlass != null) {
                //     let zeroPosition = this.getZeroPosition(this.draggedEditorGlass.value.shapeId, this.draggedEditorGlass.value.node.position);
                //     let targetIdx = this.getIdxByPos(zeroPosition);
                //     if (targetIdx == null) { // 错误位置返回
                //         this.setGlassToPositionId(editorGlass, editorGlass.posId.value);
                //     } else {
                //         this.setGlassToPositionId(editorGlass, targetIdx);
                //         editorGlass.posId.value = targetIdx;
                //     }
                //     this.draggedEditorGlass.value = null;
                //     this.remakeStageInfo();
                // }
                break;
            }
        }

        if (type == 'touch-end-click') {
            let position = this.getComponent(UITransform).convertToNodeSpaceAR(worldPosition);
            let idx = this.getIdxByPos(position);
            this.handleClickIdx(idx);
        }
    }

    handleClickIdx(idx: number) {
        this.log('@handleClickIdx', idx);
        let edited = false;

        if (gm.editToolsSelected.value == 'barrier') {
            if (this.barrierMap.has(idx)) {
                let editorBarrier = this.barrierMap.get(idx);
                editorBarrier.node.destroy();
                this.barrierMap.delete(idx);
            } else {
                this.addBarrier(idx, gm.editColorSelected.value);
            }
            edited = true;
        } else if (gm.editToolsSelected.value == 'wall') {

            let editorGround = this.groundMap.get(idx);
            if (editorGround) {
                if (this.isEdgeWall(idx)) {
                    editorGround.isWall.value = true;
                    edited = true;
                } else {
                    editorGround.isWall.value = !editorGround.isWall.value;
                    edited = true;
                }
            }

        } else if (gm.editCtrlPressed.value) {
            // 如果点击玻璃的对应位置，则设定钉子颜色
            for (let [glassId, editorGlass] of this.glassMap) {
                if (!editorGlass.node.active) continue;
                let shapeIdxs = this.getShapeOccupyIdxs(editorGlass.shapeId, editorGlass.posId.value);
                for (let i = 0; i < shapeIdxs.length; i++) {
                    if (shapeIdxs[i] == idx) {
                        let nails = editorGlass.nails.value;
                        nails[i] = gm.editColorSelected.value;
                        editorGlass.nails.value = nails;
                        edited = true;
                    }
                }
            }
            for (let [blockId, editorBlock] of this.blockMap) {
                if (!editorBlock.node.active) continue;
                let shapeIdxs = this.getShapeOccupyIdxs(editorBlock.shapeId, editorBlock.posId.value);
                for (let i = 0; i < shapeIdxs.length; i++) {
                    if (shapeIdxs[i] == idx) {
                        let nails = editorBlock.keys.value;
                        nails[i] = gm.editColorSelected.value;
                        editorBlock.keys.value = nails;
                        edited = true;
                    }
                }
            }
        }
        if (edited) {
            this.remakeStageInfo();
        }
    }

    fillDragGroupList(rootEditorBlock: EditorBlock) {
        this.dragGroupedList.splice(0, this.dragGroupedList.length);
        if (gm.editShiftPressed.value) {
            let groupId = rootEditorBlock.group.value;
            for (let [blockId, editorBlock] of this.blockMap) {
                if (editorBlock.group.value == groupId) {
                    if (editorBlock != rootEditorBlock) {
                        let offset = v3(editorBlock.node.position).subtract(rootEditorBlock.node.position);
                        this.dragGroupedList.push({ editorBlock, offset });
                    }
                }
            }
        }
    }

    moveDragGroupList(rootPosition: Vec3) {
        for (let i = 0; i < this.dragGroupedList.length; i++) {
            let { editorBlock, offset } = this.dragGroupedList[i];
            let pos = v3(rootPosition).add(offset);
            editorBlock.node.setPosition(pos);
        }
    }

    endDragGroupList(rootPosition: Vec3) {
        for (let i = 0; i < this.dragGroupedList.length; i++) {
            let { editorBlock, offset } = this.dragGroupedList[i];
            let pos = v3(rootPosition).add(offset);
            let zeroPosition = this.getZeroPosition(editorBlock.shapeId, pos);
            let targetIdx = this.getIdxByPos(zeroPosition);
            if (targetIdx == null) { // 错误位置返回
                this.setBlockToPositonId(editorBlock, editorBlock.posId.value);
            } else {
                this.setBlockToPositonId(editorBlock, targetIdx);
                editorBlock.posId.value = targetIdx;
            }
        }
    }

    getZeroPosition(shapeId: number, oriPos: Vec3) {
        let shapeInfo = shapeInfoMap.get(shapeId);
        let position = v3().set(oriPos);
        position.subtract3f(shapeInfo.center[1] * EditorStageView.Space.x, -shapeInfo.center[0] * EditorStageView.Space.y, 0);
        return position;
    }

    // getBlockZeroPosition(editorBlock: EditorBlock) {
    //     let shapeId = editorBlock.shapeId;
    //     let shapeInfo = shapeInfoMap.get(shapeId);
    //     let position = v3().set(editorBlock.node.position);
    //     position.subtract3f(shapeInfo.center[1] * EditorStageView.Space.x, -shapeInfo.center[0] * EditorStageView.Space.y, 0);
    //     return position;
    // }
    // getGlassZeroPosition(editorGlass: EditorGlass) {
    //     let shapeId = editorGlass.shapeId;
    //     let shapeInfo = shapeInfoMap.get(shapeId);
    //     let position = v3().set(editorGlass.node.position);
    //     position.subtract3f(shapeInfo.center[1] * EditorStageView.Space.x, -shapeInfo.center[0] * EditorStageView.Space.y, 0);
    //     return position;
    // }

    _sub_block_move_animation: Subscription = null;
    setBlockToPositonId(editorBlock: EditorBlock, posId: number) {
        this._sub_block_move_animation = new Observable(observer => {
            let targetPosition = this.genShapePos(posId, editorBlock.shapeId);
            let _sub = TweenToStart(editorBlock.node, .1, { position: targetPosition }, { easing: 'quadInOut' }).subscribe({
                next: _ => {
                    this.blockInspectorMap.get(editorBlock.blockId.value)?.refreshGraphics();
                },
                complete: () => {
                    observer.nextAndComplete(null);
                }
            });
            return () => {
                _sub.unsubscribe();
            }
        }).subscribe();
    }
    _sub_arrow_move_animation: Subscription = null;
    setArrowToPositonId(editorArrow: EditorArrow, posId: number) {
        this._sub_block_move_animation = new Observable(observer => {
            let targetPosition = this.getPositionByIdx(posId);
            let _sub = TweenToStart(editorArrow.node, .1, { position: targetPosition }, { easing: 'quadInOut' }).subscribe({
                next: _ => {
                    this.arrowInspectorMap.get(editorArrow.arrowId.value)?.refreshGraphics();
                },
                complete: () => {
                    observer.nextAndComplete(null);
                }
            });
            return () => {
                _sub.unsubscribe();
            }
        }).subscribe();
    }
    setGlassToPositionId(editorGlass: EditorGlass, posId: number) {
        this._sub_block_move_animation = new Observable(observer => {
            let targetPosition = this.genShapePos(posId, editorGlass.shapeId);
            let _sub = TweenToStart(editorGlass.node, .1, { position: targetPosition }, { easing: 'quadInOut' }).subscribe({
                next: _ => {
                    this.glassInspectorMap.get(editorGlass.glassId.value)?.refreshGraphics();
                },
                complete: () => {
                    observer.nextAndComplete(null);
                }
            });
            return () => {
                _sub.unsubscribe();
            }
        }).subscribe();
    }

    isOccupy(posId: number) {
        return this.blockMap.has(posId);
    }

    genBlockId() {
        let idsAlreadyHave = new Set();
        for (let [posId, editorBlock] of this.blockMap) {
            let id = editorBlock?.blockId.value;
            if (id != null) {
                idsAlreadyHave.add(id);
            }
        }
        let newId = 0;
        let has = true;
        do {
            has = idsAlreadyHave.has(newId);
            if (!has) {
                return newId;
            }
            newId++;
        } while (has);
    }

    genBlockGroupId() {
        let idsAlreadyHave = new Set();
        for (let [posId, editorBlock] of this.blockMap) {
            let id = editorBlock?.group.value;
            if (id != null) {
                idsAlreadyHave.add(id);
            }
        }
        let newId = 0;
        let has = true;
        do {
            has = idsAlreadyHave.has(newId);
            if (!has) {
                return newId;
            }
            newId++;
        } while (has);
    }

    addBarrier(posIdx: number, colorId: number) {
        let posId = posIdx;
        let node = instantiate(this.editorBarrierPrefab);
        let editorBarrier = node.getComponent(EditorBarrier);
        editorBarrier.colorId = colorId;
        let pos = this.getPositionByIdx(posId);
        node.setParent(this.groundContainer);
        node.setPosition(pos);
        this.barrierMap.set(posId, editorBarrier);
    }

    blockMap = new Map<number, EditorBlock>();
    addBlock(posIdx: number, shapeId: number, blockId?: number): EditorBlock {
        let blockNode = instantiate(this.editorBlockPrefab);
        blockNode.setParent(this.blockContainer);
        let shapeInfo = shapeInfoMap.get(shapeId);
        let block = blockNode.getComponent(EditorBlock);
        block.blockId.value = blockId ?? this.genBlockId();
        block.group.value = this.genBlockGroupId();
        block.posId.value = posIdx;
        block.shapeId = shapeId;
        block.colorId = gm.editColorSelected.value;
        block.keys.value = Array.from({ length: shapeInfo.data.length }, (_, _idx) => 0);
        let blockPos = this.genShapePos(posIdx, shapeId);
        blockNode.setPosition(blockPos);
        this.blockMap.set(block.blockId.value, block);
        gm.blocksInfoRefreshCommand.execute();
        return block;
    }

    removeBlock(blockId: number) {
        let lastBlock = this.blockMap.get(blockId);
        if (lastBlock != null) {
            this.closeBlockInspector(lastBlock.blockId.value);
        }
        if (lastBlock?.node?.isValid) {
            lastBlock.node.destroy();
        }
        this.blockMap.delete(blockId);
        gm.blocksInfoRefreshCommand.execute();
    }

    genArrowId() {
        let idsAlreadyHave = new Set();
        for (let [posId, editorArrow] of this.arrowMap) {
            let id = editorArrow?.arrowId.value;
            if (id != null) {
                idsAlreadyHave.add(id);
            }
        }
        let newId = 0;
        let has = true;
        do {
            has = idsAlreadyHave.has(newId);
            if (!has) {
                return newId;
            }
            newId++;
        } while (has);
    }

    arrowMap = new Map<number, EditorArrow>();

    addArrow(posIdx: number, direction: number, arrowId?: number): EditorArrow {
        let arrowNode = instantiate(this.editorArrowPrefab);
        arrowNode.setParent(this.arrowContainer);
        let arrow = arrowNode.getComponent(EditorArrow);
        arrow.arrowId.value = arrowId ?? this.genArrowId();
        arrow.posId.value = posIdx;
        arrow.direction = direction;
        arrow.colorId = gm.editColorSelected.value;
        let pos = this.getPositionByIdx(posIdx);
        arrowNode.setPosition(pos);
        this.arrowMap.set(arrow.arrowId.value, arrow);
        gm.arrowsInfoRefreshCommand.execute();

        this.makeMirrorArrow(arrow);

        return arrow;
    }

    removeArrow(arrowId: number) {
        let lastArrow = this.arrowMap.get(arrowId);
        if (lastArrow != null) {
            this.destroyMirrorArrow(lastArrow);
            this.closeArrowInspector(lastArrow.arrowId.value);
        }
        if (lastArrow?.node?.isValid) {
            lastArrow.node.destroy();
        }
        this.arrowMap.delete(arrowId);
        gm.arrowsInfoRefreshCommand.execute();
    }

    genGlassId() {
        let idsAlreadyHave = new Set();
        for (let [posId, node] of this.glassMap) {
            let id = node.getComponent(EditorGlass)?.glassId.value;
            if (id != null) {
                idsAlreadyHave.add(id);
            }
        }
        let newId = 0;
        let has = true;
        do {
            has = idsAlreadyHave.has(newId);
            if (!has) {
                return newId;
            }
            newId++;
        } while (has);
    }

    glassMap = new Map<number, EditorGlass>();
    addGlass(posIdx: number, shapeId: number, glassId?: number): EditorGlass {
        let shapeInfo = shapeInfoMap.get(shapeId);
        let glassNode = instantiate(this.editorGlassPrefab);
        glassNode.setParent(this.glassContainer);
        let glass = glassNode.getComponent(EditorGlass);
        glass.glassId.value = glassId ?? this.genGlassId();
        glass.posId.value = posIdx;
        glass.shapeId = shapeId;
        glass.nails.value = Array.from({ length: shapeInfo.data.length }, (_, _idx) => gm.editColorSelected.value);
        glass.layer.value = 0;
        glass.lock.value = 0;
        let shapePos = this.genShapePos(posIdx, shapeId);
        glassNode.setPosition(shapePos);
        this.glassMap.set(glass.glassId.value, glass);
        gm.glassesInfoRefreshCommand.execute();
        return glass;
    }

    removeGlass(glassId: number) {
        let lastGlass = this.glassMap.get(glassId);
        if (lastGlass != null) {
            this.closeGlassInspector(lastGlass.glassId.value);
        }
        if (lastGlass?.node?.isValid) {
            lastGlass.node.destroy();
        }
        this.glassMap.delete(glassId);
        gm.glassesInfoRefreshCommand.execute();
    }

    findBlockByWorldPos(worldPosition: Vec3): EditorBlock[] {
        let position = this.blockContainer.getComponent(UITransform).convertToNodeSpaceAR(worldPosition);
        let idx = this.getIdxByPos(position);
        let result: EditorBlock[] = [];
        for (let [blockId, editorBlock] of this.blockMap) {
            let blockIdxs = this.getShapeOccupyIdxs(editorBlock.shapeId, editorBlock.posId.value);
            if (blockIdxs.includes(idx)) {
                result.push(editorBlock);
            }
        }
        return result;
    }

    findArrowByWorldPos(worldPosition: Vec3): EditorArrow[] {
        let position = this.arrowContainer.getComponent(UITransform).convertToNodeSpaceAR(worldPosition);
        let idx = this.getIdxByPos(position);
        let result: EditorArrow[] = [];
        for (let [arrowId, editorArrow] of this.arrowMap) {
            if (editorArrow.posId.value == idx || editorArrow.mirrorEditorArrow.value.posId.value == idx) {
                result.push(editorArrow);
            }
        }
        return result;
    }

    findGlassByWorldPos(worldPosition: Vec3): EditorGlass[] {
        let position = this.blockContainer.getComponent(UITransform).convertToNodeSpaceAR(worldPosition);
        let idx = this.getIdxByPos(position);
        let result: EditorGlass[] = [];
        for (let [glassId, editorGlass] of this.glassMap) {
            let shapeIdxs = this.getShapeOccupyIdxs(editorGlass.shapeId, editorGlass.posId.value);
            if (shapeIdxs.includes(idx)) {
                result.push(editorGlass);
            }
        }
        return result;
    }

    getShapeOccupyIdxs(shapeId: number, posId: number) {
        let idxs: number[] = [];
        let rcPos = this.getRowColPos(posId);
        let shapeInfo = shapeInfoMap.get(shapeId);
        for (let i = 0; i < shapeInfo.data.length; i++) {
            let offset = [rcPos[0], rcPos[1]];
            let [y, x] = shapeInfo.data[i];
            offset[0] += y;
            offset[1] += x;
            let idx = this.getIdxByRowCol(offset);
            idxs.push(idx);
        }
        return idxs;
    }

    // 平移地图
    draggedStage = false;
    startDragPosition = v3();
    startStagePosition = v3();
    handleTouchInEditModePanStage(param: { type: string, eventTouch: EventTouch, worldPosition: Vec3 }) {
        let { type, eventTouch, worldPosition } = param;
        switch (type) {
            case 'touch-start':
            case 'touch-move': {
                if (this.draggedStage == false) {
                    this.draggedStage = true;
                    this.startDragPosition.set(worldPosition);
                    this.startStagePosition.set(this.node.position);
                }
                if (this.draggedStage) {
                    let dragVector = v3(worldPosition).subtract(this.startDragPosition);
                    let targetVector = v3(this.startStagePosition).add(dragVector);
                    this.node.setPosition(targetVector);
                }
                break;
            }
            case 'touch-cancel':
            case 'touch-end':
            case 'touch-end-click': {
                this.draggedStage = false;
                break;
            }
        }
    }


    // block inspector
    tryOpenBlockInspector(blockId: number) {
        let editorBlock = this.blockMap.get(blockId);
        if (editorBlock != null) {
            this.closeAllInspectors();

            if (this.blockInspectorMap.has(editorBlock.blockId.value)) return;

            let inspectorNode = instantiate(this.editorBlockInspectorPrefab);
            inspectorNode.setParent(this.inspectorContainer);

            let inspector = inspectorNode.getComponent(EditorBlockInspector);
            inspector.editorStageView = this;
            inspector.editorBlock.value = editorBlock;

            let containerSize = this.inspectorContainer.getComponent(UITransform).contentSize;
            let inspectorSize = this.node.getComponent(UITransform).contentSize;
            inspectorSize = size(inspectorSize.width / 2, inspectorSize.height / 2);
            let pos = v3(
                Math.max(-1000, -containerSize.width / 2 + inspectorSize.width / 2) + editorBlock.blockId.value * 20,
                -containerSize.height / 2 + inspectorSize.height / 2 + editorBlock.blockId.value * 20 - 300,
                0
            );
            inspectorNode.setPosition(pos);
            this.blockInspectorMap.set(editorBlock.blockId.value, inspector);
            nc.post('block_inspector_change', null);
        }
    }

    closeAllBlockInspectors(all = false) {
        let inspectors: EditorBlockInspector[] = [];
        for (let [idx, inspector] of this.blockInspectorMap) {
            if (all) {
                inspectors.push(inspector);
            } else {
                if (!inspector.pined.value) {
                    inspectors.push(inspector);
                }
            }
        }
        inspectors.forEach(_ => this.closeBlockInspector(_.editorBlock.value.blockId.value));
    }

    closeBlockInspector(blockId: number) {
        let inspector = this.blockInspectorMap.get(blockId);
        if (inspector?.node?.isValid) {
            inspector.node.destroy();
        }
        this.blockInspectorMap.delete(blockId);
        nc.post('block_inspector_change', null);
    }


    // arrow inspector
    tryOpenArrowInspector(arrowId: number) {
        let editorArrow = this.arrowMap.get(arrowId);
        if (editorArrow != null) {
            this.closeAllInspectors();

            if (this.arrowInspectorMap.has(editorArrow.arrowId.value)) return;

            let inspectorNode = instantiate(this.editorArrowInspectorPrefab);
            inspectorNode.setParent(this.inspectorContainer);

            let inspector = inspectorNode.getComponent(EditorArrowInspector);
            inspector.editorStageView = this;
            inspector.editorArrow.value = editorArrow;

            let containerSize = this.inspectorContainer.getComponent(UITransform).contentSize;
            let inspectorSize = this.node.getComponent(UITransform).contentSize;
            inspectorSize = size(inspectorSize.width / 2, inspectorSize.height / 2);
            let pos = v3(
                Math.max(-1000, -containerSize.width / 2 + inspectorSize.width / 2) + editorArrow.arrowId.value * 20 + 200,
                -containerSize.height / 2 + inspectorSize.height / 2 + editorArrow.arrowId.value * 20 - 300,
                0
            );
            inspectorNode.setPosition(pos);
            this.arrowInspectorMap.set(editorArrow.arrowId.value, inspector);
            nc.post('arrow_inspector_change', null);
        }
    }

    closeAllArrowInspectors(all = false) {
        let inspectors: EditorArrowInspector[] = [];
        for (let [idx, inspector] of this.arrowInspectorMap) {
            if (all) {
                inspectors.push(inspector);
            } else {
                if (!inspector.pined.value) {
                    inspectors.push(inspector);
                }
            }
        }
        inspectors.forEach(_ => this.closeArrowInspector(_.editorArrow.value.arrowId.value));
    }

    closeArrowInspector(arrowId: number) {
        let inspector = this.arrowInspectorMap.get(arrowId);
        if (inspector?.node?.isValid) {
            inspector.node.destroy();
        }
        this.arrowInspectorMap.delete(arrowId);
        nc.post('arrow_inspector_change', null);
    }

    // glass inspector
    tryOpenGlassInspector(glassId: number) {
        let editorGlass = this.glassMap.get(glassId);
        if (editorGlass != null) {
            this.closeAllInspectors();

            if (this.glassInspectorMap.has(editorGlass.glassId.value)) return;

            let inspectorNode = instantiate(this.editorGlassInspectorPrefab);
            inspectorNode.setParent(this.inspectorContainer);

            let inspector = inspectorNode.getComponent(EditorGlassInspector);
            inspector.editorStageView = this;
            inspector.editorGlass.value = editorGlass;

            let containerSize = this.inspectorContainer.getComponent(UITransform).contentSize;
            let inspectorSize = this.node.getComponent(UITransform).contentSize;
            inspectorSize = size(inspectorSize.width / 2, inspectorSize.height / 2);
            let pos = v3(
                Math.max(-1000, -containerSize.width / 2 + inspectorSize.width / 2) + editorGlass.glassId.value * 20,
                -containerSize.height / 2 + inspectorSize.height / 2 + editorGlass.glassId.value * 20 - 300,
                0
            );
            inspectorNode.setPosition(pos);
            this.glassInspectorMap.set(editorGlass.glassId.value, inspector);
            nc.post('glass_inspector_change', null);
        }
    }

    closeAllGlassInspectors(all = false) {
        let inspectors: EditorGlassInspector[] = [];
        for (let [idx, inspector] of this.glassInspectorMap) {
            if (all) {
                inspectors.push(inspector);
            } else {
                if (!inspector.pined.value) {
                    inspectors.push(inspector);
                }
            }
        }
        inspectors.forEach(_ => this.closeGlassInspector(_.editorGlass.value.glassId.value));
    }

    closeGlassInspector(glassId: number) {
        let inspector = this.glassInspectorMap.get(glassId);
        if (inspector?.node?.isValid) {
            inspector.node.destroy();
        }
        this.glassInspectorMap.delete(glassId);
        nc.post('glass_inspector_change', null);
    }

    closeAllInspectors(all = false) {
        this.closeAllBlockInspectors(all);
        this.closeAllArrowInspectors(all);
        this.closeAllGlassInspectors(all);
    }

    glassExplorer: EditorGlassExplorer = null;
    tryOpenGlassExplorer() {
        if (this.glassExplorer == null) {
            let explorerNode = instantiate(this.editorGlassExplorerPrefab);
            explorerNode.setParent(this.inspectorContainer);

            let explorer = explorerNode.getComponent(EditorGlassExplorer);
            explorer.editorStageView = this;

            let containerSize = this.inspectorContainer.getComponent(UITransform).contentSize;
            let explorerSize = this.node.getComponent(UITransform).contentSize;
            explorerSize = size(explorerSize.width / 2, explorerSize.height / 2);
            let pos = v3(
                Math.max(-1000, -containerSize.width / 2 + explorerSize.width / 2) - 400,
                -containerSize.height / 2 + explorerSize.height / 2,
                0
            );
            explorerNode.setPosition(pos);
            this.glassExplorer = explorer;
            gm.glassesInfoRefreshCommand.execute();
        }
    }
    closeGlassExplorer() {
        if (this.glassExplorer?.node?.isValid) {
            this.glassExplorer.node.destroy();
            this.glassExplorer = null;
        }
    }


    blockExplorer: EditorBlockExplorer = null;
    tryOpenBlockExplorer() {
        if (this.blockExplorer == null) {
            let explorerNode = instantiate(this.editorBlockExplorerPrefab);
            explorerNode.setParent(this.inspectorContainer);

            let explorer = explorerNode.getComponent(EditorBlockExplorer);
            explorer.editorStageView = this;

            let containerSize = this.inspectorContainer.getComponent(UITransform).contentSize;
            let explorerSize = this.node.getComponent(UITransform).contentSize;
            explorerSize = size(explorerSize.width / 2, explorerSize.height / 2);
            let pos = v3(
                Math.max(-1000, -containerSize.width / 2 + explorerSize.width / 2) - 300,
                -containerSize.height / 2 + explorerSize.height / 2 + 200,
                0
            );
            explorerNode.setPosition(pos);
            this.blockExplorer = explorer;
            gm.blocksInfoRefreshCommand.execute();
        }
    }
    closeBlockExplorer() {
        if (this.blockExplorer?.node?.isValid) {
            this.blockExplorer.node.destroy();
            this.blockExplorer = null;
        }
    }

    arrowExplorer: EditorArrowExplorer = null;
    tryOpenArrowExplorer() {
        if (this.arrowExplorer == null) {
            let explorerNode = instantiate(this.editorArrowExplorerPrefab);
            explorerNode.setParent(this.inspectorContainer);

            let explorer = explorerNode.getComponent(EditorArrowExplorer);
            explorer.editorStageView = this;

            let containerSize = this.inspectorContainer.getComponent(UITransform).contentSize;
            let explorerSize = this.node.getComponent(UITransform).contentSize;
            explorerSize = size(explorerSize.width / 2, explorerSize.height / 2);
            let pos = v3(
                Math.max(-1000, -containerSize.width / 2 + explorerSize.width / 2) - 200,
                -containerSize.height / 2 + explorerSize.height / 2,
                0
            );
            explorerNode.setPosition(pos);
            this.arrowExplorer = explorer;
            gm.arrowsInfoRefreshCommand.execute();
        }
    }
    closeArrowExplorer() {
        if (this.arrowExplorer?.node?.isValid) {
            this.arrowExplorer.node.destroy();
            this.arrowExplorer = null;
        }
    }

    stageInspector: EditorStageInspector = null;
    tryOpenStageInspector() {
        if (this.stageInspector == null) {
            let inspectorNode = instantiate(this.editorStageInspectorPrefab);
            let inspector = inspectorNode.getComponent(EditorStageInspector);
            inspector.editorStageView = this;
            inspectorNode.setParent(this.inspectorContainer);

            let containerSize = this.inspectorContainer.getComponent(UITransform).contentSize;
            let explorerSize = this.node.getComponent(UITransform).contentSize;
            explorerSize = size(explorerSize.width / 2, explorerSize.height / 2);
            let pos = v3(
                Math.max(-1000, -containerSize.width / 2 + explorerSize.width / 2) - 400,
                -containerSize.height / 2 + explorerSize.height / 2 + 400,
                0
            );
            inspectorNode.setPosition(pos);
            this.stageInspector = inspector;
        }
    }
    closeStageInspector() {
        if (this.stageInspector?.node?.isValid) {
            this.stageInspector.node.destroy();
            this.stageInspector = null;
        }
    }

    _oldStageInfoStrList: string[] = [];

    remakeStageInfo() {
        // 保存
        this._oldStageInfoStrList.push(JSON.stringify(this.stageInfo));
        if (this._oldStageInfoStrList.length > 100) this._oldStageInfoStrList.shift();

        this.stageInfo.stageId = this.stageNum.value ?? 0;
        this.stageInfo.time = this.time.value ?? 180;
        this.stageInfo.hard = this.hard.value ?? 0;
        this.stageInfo.tip = this.tip.value ?? '';

        this.stageInfo.walls = [];
        for (let [posId, editorGround] of this.groundMap) {
            if (editorGround.isWall.value) {
                this.stageInfo.walls.push(posId);
            }
        }

        this.stageInfo.barriers = [];
        for (let [posId, editorBarrier] of this.barrierMap) {
            this.stageInfo.barriers.push([posId, editorBarrier.colorId]);
        }

        this.stageInfo.blocks = [];
        for (let [blockId, editorBlock] of this.blockMap) {
            let block = {
                id: editorBlock.blockId.value,
                pos: editorBlock.posId.value,
                shape: editorBlock.shapeId,
                color: editorBlock.colorId,
                axisLock: editorBlock.axisLock,
                axisLockParam: StringTools.isNullOrEmpty(editorBlock.axisLockParam.value) ? undefined : editorBlock.axisLockParam.value,
                group: editorBlock.group.value,
                layer: editorBlock.layer.value,
                ice: editorBlock.ice.value > 0 ? editorBlock.ice.value : undefined,
                blind: editorBlock.blind.value > 0 ? editorBlock.blind.value : undefined,
                noBlindTxt: editorBlock.noBlindTxt.value > 0 ? editorBlock.noBlindTxt.value : undefined,
                keys: editorBlock.keys.value,
                locker: editorBlock.lockerColor.value ?? undefined,
            };
            this.stageInfo.blocks.push(block);
        }
        this.stageInfo.blocks.sort((a, b) => a.layer - b.layer);

        this.stageInfo.arrows = [];
        for (let [arrowId, editorArrow] of this.arrowMap) {
            let arrow = {
                id: editorArrow.arrowId.value,
                pos: editorArrow.posId.value,
                color: editorArrow.colorId,
                direction: editorArrow.direction,
                layer: editorArrow.layer.value,
                prev: editorArrow.prev.value ?? undefined,
                ice: editorArrow.ice.value > 0 ? editorArrow.ice.value : undefined,
            }
            this.stageInfo.arrows.push(arrow);
        }
        this.stageInfo.arrows.sort((a, b) => a.layer - b.layer);

        this.stageInfo.glasses = [];
        for (let [glassId, editorGlass] of this.glassMap) {
            let glass = {
                id: editorGlass.glassId.value,
                pos: editorGlass.posId.value,
                shape: editorGlass.shapeId,
                nails: editorGlass.nails.value,
                layer: editorGlass.layer.value,
                lock: editorGlass.lock.value,
            }
            this.stageInfo.glasses.push(glass);
        }
        this.stageInfo.glasses.sort((a, b) => a.layer - b.layer);

        gm.glassesInfoRefreshCommand.execute();
        gm.blocksInfoRefreshCommand.execute();
        gm.arrowsInfoRefreshCommand.execute();
    }

    undo() {
        if (this._oldStageInfoStrList.length > 0) {
            let stageInfo = JSON.parse(this._oldStageInfoStrList.pop());
            gm.loadStageCommand.execute(stageInfo);
        }
    }

    reorderBlocks() {
        let list: EditorBlock[] = [];
        for (let [blockId, editorBlock] of this.blockMap) {
            list.push(editorBlock);
            editorBlock.node.removeFromParent();
        }
        list.sort((a, b) => a.layer.value - b.layer.value);
        for (let editorBlock of list) {
            editorBlock.node.setParent(this.blockContainer);
        }
    }

    reorderGlasses() {
        let list: EditorGlass[] = [];
        for (let [glassId, editorGlass] of this.glassMap) {
            list.push(editorGlass);
            editorGlass.node.removeFromParent();
        }
        list.sort((a, b) => a.layer.value - b.layer.value);
        for (let editorGlass of list) {
            editorGlass.node.setParent(this.glassContainer);
        }
    }

    reorderArrows() {
        let list: EditorArrow[] = [];
        for (let [glassId, editorArrow] of this.arrowMap) {
            list.push(editorArrow);
            if (editorArrow.mirrorEditorArrow != null) {
                list.push(editorArrow.mirrorEditorArrow.value);
            }
            editorArrow.node.removeFromParent();
        }
        list.sort((a, b) => a.layer.value - b.layer.value);
        for (let editorArrow of list) {
            editorArrow.node.setParent(this.arrowContainer);
        }
    }

    makeMirrorArrow(editorArrow: EditorArrow) {

        this.destroyMirrorArrow(editorArrow);

        let oriIdx = editorArrow.posId.value;
        let direction = editorArrow.direction;

        // 查找mirror 位置
        let targetIdx = oriIdx;
        let [row, col] = this.getRowColPos(targetIdx);

        let got = true;

        do {
            if (direction == 0) {
                col++;
            } else if (direction == 1) {
                row--;
            } else if (direction == 2) {
                col--;
            } else if (direction == 3) {
                row++;
            }

            if (this.isInRange(row, col)) {
                targetIdx = this.getIdxByRowCol([row, col]);
                let isWall = this.groundMap.get(targetIdx).isWall.value;
                if (isWall) {
                    got = true;
                    break;
                }
            } else {
                break;
            }

        } while (true);


        if (got) {
            let arrowNode = instantiate(this.editorArrowPrefab);
            arrowNode.setParent(this.arrowContainer);
            arrowNode.setSiblingIndex(editorArrow.node.getSiblingIndex() + 1);
            let arrow = arrowNode.getComponent(EditorArrow);
            arrow.arrowId.value = -1;
            arrow.posId.value = targetIdx;
            // arrow.direction = (direction + 2) % 4; // 取反向
            // arrow.colorId = editorArrow.colorId;
            // arrow.layer = editorArrow.layer;
            let pos = this.getPositionByIdx(targetIdx);
            arrowNode.setPosition(pos);

            editorArrow.mirrorEditorArrow.value = arrow;
        }

    }

    destroyMirrorArrow(editorArrow: EditorArrow) {
        if (editorArrow.mirrorEditorArrow.value?.node?.isValid) {
            editorArrow.mirrorEditorArrow.value?.node?.destroy();
            editorArrow.mirrorEditorArrow.value = null;
        }
    }

    // 模拟计算消除一列之后的情况，输出原参与计算的 block 和 最终拆分后的新 block

    _ex_group_id = 100;
    genExGroupId() {
        return ++this._ex_group_id;
    }

    cutResultInfoList: { editorBlock: EditorBlock, shapeId: number, posIdx: number, group?: number }[] = [];

    calcJointGroup() {
        this.log('>> result:', this.cutResultInfoList);
        // 把同一个 groupId 做分组
        let groupSet = new Map<number, { editorBlock: EditorBlock, shapeId: number, posIdx: number, group?: number }[]>();
        for (let item of this.cutResultInfoList) {
            let groupId = item.editorBlock.group.value;
            if (!groupSet.has(groupId)) groupSet.set(groupId, []);
            groupSet.get(groupId).push(item);
        }
        // this.log('>> groupSet', groupSet);
        for (let [groupId, infoItemList] of groupSet) {
            let joints = this.getJoint(groupId);
            this.log('>>', infoItemList, joints);

            let virtualOccupyMap = new Map<number, number>();
            for (let i = 0; i < infoItemList.length; i++) {
                let infoItem = infoItemList[i];
                let idxs = this.getShapeOccupyIdxs(infoItem.shapeId, infoItem.posIdx);
                for (let idx of idxs) {
                    virtualOccupyMap.set(idx, i);
                }
            }

            for (let joint of joints) {
                let [p0, p1] = joint;
                let idx0 = virtualOccupyMap.get(p0);
                let idx1 = virtualOccupyMap.get(p1);
                if (idx0 != null && idx0 != idx1) {
                    let groupId = this.genExGroupId();
                    infoItemList[idx0].group = groupId;
                    infoItemList[idx1].group = groupId;
                }
            }

            for (let itemInfo of infoItemList) {
                if (itemInfo.group == null) {
                    itemInfo.group = this.genExGroupId();
                }
            }
        }

        // 此时 groupSet 和 curResultInfoList里面的信息够创建新的 block，而其中的 editorBlock 则需要删除（注意有重复）
    }

    CutCol(cutCol: number) {
        this.cutResultInfoList = [];
        // 获得需要参与计算的原始 block
        let editorBlocks = new Set<EditorBlock>();
        for (let [blockId, editorBlock] of this.blockMap) {
            let idxs = this.getShapeOccupyIdxs(editorBlock.shapeId, editorBlock.posId.value);
            for (let idx of idxs) {
                let [row, col] = this.getRowColPos(idx);
                if (col == cutCol) {
                    editorBlocks.add(editorBlock);
                }
            }
        }
        for (let editorBlock of editorBlocks) {
            this.log('CutCol:', editorBlock);
            this.CutBlockAtCol(editorBlock, cutCol);
        }
        this.calcJointGroup();
    }

    CutBlockAtCol(editorBlock: EditorBlock, cutCol: number) {
        // cutCol相当于 data 内的第几 col
        let [zeroRow, zeroCol] = this.getRowColPos(editorBlock.posId.value);
        let _localCol = cutCol - zeroCol;
        let data = deepclone(shapeInfoMap.get(editorBlock.shapeId).data);
        // 把data按照_localCol分两部分
        let data0 = data.filter(([_r, _c]) => _c < _localCol);
        let data1 = data.filter(([_r, _c]) => _c > _localCol);
        this.dataForNewBlock([data0, data1], editorBlock);

    }

    CutRow(cutRow: number) {
        // 获得需要参与计算的原始 block
        let editorBlocks = new Set<EditorBlock>();
        for (let [blockId, editorBlock] of this.blockMap) {
            let idxs = this.getShapeOccupyIdxs(editorBlock.shapeId, editorBlock.posId.value);
            for (let idx of idxs) {
                let [row, col] = this.getRowColPos(idx);
                if (row == cutRow) {
                    editorBlocks.add(editorBlock);
                }
            }
        }
        for (let editorBlock of editorBlocks) {
            this.log('CutRow:', editorBlock);
            this.CutBlockAtRow(editorBlock, cutRow);
        }
        this.calcJointGroup();
    }

    CutBlockAtRow(editorBlock: EditorBlock, curRow: number) {
        // cutCol相当于 data 内的第几 col
        let [zeroRow, zeroCol] = this.getRowColPos(editorBlock.posId.value);
        let _localRow = curRow - zeroRow;
        let data = deepclone(shapeInfoMap.get(editorBlock.shapeId).data);
        // 把data按照_localCol分两部分
        let data0 = data.filter(([_r, _c]) => _r < _localRow);
        let data1 = data.filter(([_r, _c]) => _r > _localRow);
        this.dataForNewBlock([data0, data1], editorBlock);
    }

    dataForNewBlock(dataList: number[][][], editorBlock: EditorBlock) {
        let [zeroRow, zeroCol] = this.getRowColPos(editorBlock.posId.value);
        dataList.forEach(_data => {
            if (_data.length > 0) {
                let firstRC = deepclone(_data[0]);
                for (let i = 0; i < _data.length; i++) {
                    _data[i] = [_data[i][0] - firstRC[0], _data[i][1] - firstRC[1]];
                }
                let shapeId = findShapeId(_data);
                let rc = [zeroRow + firstRC[0], zeroCol + firstRC[1]];
                let idx = this.getIdxByRowCol(rc);
                this.log(`shapeId:${shapeId} idx:${idx}`);
                this.cutResultInfoList.push({ editorBlock, shapeId, posIdx: idx });
            }
        });
    }

    // 获得所有关联的上层 block，也就是移动后会走的
    getTopBlock(editorBlock: EditorBlock): EditorBlock[] {
        let editorBlocks = new Set<EditorBlock>();
        let idxs = this.getShapeOccupyIdxs(editorBlock.shapeId, editorBlock.posId.value);
        for (let [blockId, _editorBlock] of this.blockMap) {
            if (_editorBlock.layer.value > editorBlock.layer.value) {
                let _idxSet = new Set(this.getShapeOccupyIdxs(_editorBlock.shapeId, _editorBlock.posId.value));
                for (let idx of idxs) {
                    if (_idxSet.has(idx)) {
                        editorBlocks.add(_editorBlock);
                    }
                }
            }
        }
        return Array.from(editorBlocks);
    }

    // 根据group 生成 joint
    getJoint(groupId: number) {
        /**
         * 如何生成？
         * 遍历所有可以生成的 joint，位置遍历
         * 每个 joint 对应两个坐标，
         * 这两个坐标分别有不同的 block 占用，且 block 都是 layer0的，且 block 都是同 group的
         *  */
        // 生成一个block的占用索引表
        let blockOccupyMap = new Map<number, EditorBlock>();
        for (let [blockId, editorBlock] of this.blockMap) {
            if (editorBlock.group.value == groupId) {
                if (editorBlock.layer.value == 0) {
                    let idxs = this.getShapeOccupyIdxs(editorBlock.shapeId, editorBlock.posId.value);
                    for (let idx of idxs) {
                        blockOccupyMap.set(idx, editorBlock);
                    }
                }
            }
        }

        let joints: number[][] = [];
        // 左右 joint
        for (let r = 0; r < this.Height; r++) {
            for (let c = 0; c < this.Width - 1; c++) {
                let p0 = this.getIdxByRowCol([r, c]);
                let p1 = this.getIdxByRowCol([r, c + 1]);
                let block0 = blockOccupyMap.get(p0);
                let block1 = blockOccupyMap.get(p1);
                if (block0 != null && block1 != null) {
                    if (block0 != block1) {
                        if (block0.group.value == block1.group.value) {
                            joints.push([p0, p1]);
                        }
                    }
                }
            }
        }
        // 上下 joint
        for (let r = 0; r < this.Height - 1; r++) {
            for (let c = 0; c < this.Width; c++) {
                let p0 = this.getIdxByRowCol([r, c]);
                let p1 = this.getIdxByRowCol([r + 1, c]);
                let block0 = blockOccupyMap.get(p0);
                let block1 = blockOccupyMap.get(p1);
                if (block0 != null && block1 != null) {
                    if (block0 != block1) {
                        if (block0.group.value == block1.group.value) {
                            joints.push([p0, p1]);
                        }
                    }
                }
            }
        }

        return joints;
    }


}

